using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Windows.Forms;

namespace mEdit
{
    [ToolboxBitmap( typeof(System.Windows.Forms.TextBox))]
    [DesignTimeVisible(true)]
    public partial class EditorTextBox : UserControl, ISaveableContent
    {

        private int _position;
        private bool _isContentChanged = false;

        /// <summary>
        /// gets the string contained by the control
        /// </summary>
        [DisplayName("Text")]
        [Category("EditorTextBox")]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Bindable(true)]
        [Editor(typeof(System.ComponentModel.Design.MultilineStringEditor),typeof(UITypeEditor))]
        public override string Text
        {
            get { return this.content.Text; }
            set 
            {
                this.content.Text = value ?? string.Empty;
                this.ResetContentUndoRedo();
            }
        }

        /// <summary>
        /// Gets or sets the font which controll will use (editor box and line numbers)
        /// </summary>
        [Category("EditorTextBox")]
        public Font EditorFont
        {
            get { return this.content.Font; }
            set
            {
                if (value != null)
                {
                    this.content.Font = value;
                }
            }
        }

        /// <summary>
        /// Gets or sets background color for edition part of EditorText Control
        /// </summary>
        [Category("EditorTextBox")]
        [DefaultValue(typeof(Color),"White")]
        public Color EditorBackColor
        {
            get { return this.content.BackColor; }
            set { this.content.BackColor = value; }
        }

        /// <summary>
        /// Gets or sets foreground color for edition part of EditorText Control
        /// </summary>
        [Category("EditorTextBox")]
        [DefaultValue(typeof(Color),"Black")]
        public Color EditorForeColor
        {
            get { return this.content.ForeColor; }
            set { this.content.ForeColor = value; }
        }

        /// <summary>
        /// Gets or sets foreground color for line numbering part of EditorText Control
        /// </summary>
        [Category("EditorTextBox")]
        [DefaultValue(typeof(Color), "White")]
        public Color LinesForeColor
        {
            get { return this.content.Styles.LineNumber.ForeColor; }
            set { this.content.Styles.LineNumber.ForeColor = value; }
        }

        /// <summary>
        /// Gets or sets background color for line numbering part of EditorText Control
        /// </summary>
        [Category("EditorTextBox")]
        [DefaultValue(typeof(Color), "143, 181, 218")]
        public Color LinesBackColor
        {
            get { return this.content.Styles.LineNumber.BackColor; }
            set { this.content.Styles.LineNumber.BackColor = value; }
        }

        /// <summary>
        /// Gets or sets Line Number margin widths
        /// </summary>
        [Category("EditorTextBox")]
        public int LineNumberMarginWidth
        {
            get { return this.content.Margins.Margin0.Width; }
            set { this.content.Margins.Margin0.Width = value; }
        }

        /// <summary>
        /// Gets or sets max margin value
        /// </summary>
        [Category("EditorTextBox")]
        [DefaultValue(40)]
        public int MaxMargin
        { get; set; }

        /// <summary>
        /// Gets or sets min margin value
        /// </summary>
        [Category("EditorTextBox")]
        [DefaultValue(20)]
        public int MinMargin
        { get; set; }

        /// <summary>
        /// Gets or sets default margin value
        /// </summary>
        [Category("EditorTextBox")]
        [DefaultValue(20)]
        public int MarginDefault
        { get; set; }

        public EditorTextBox()
        {
            InitializeComponent();

            this.content.DragEnter +=new DragEventHandler(content_DragEnter);
            this.content.DragDrop += new DragEventHandler(content_DragDrop);
            this.content.CharAdded += new System.EventHandler<ScintillaNet.CharAddedEventArgs>(this.content_CharAdded);
            this.content.PreviewKeyDown += (sender, args) => this.content_PreviewKeyDown(sender, args);
            this.content.KeyDown += (sender, args) => this.content_KeyDown(sender, args);
            this.MaxMargin = 40;
            this.MaxMargin = 20;


            this.content.Styles.LineNumber.BackColor = System.Drawing.Color.FromArgb(143, 181, 218);
            this.content.Styles.LineNumber.ForeColor = System.Drawing.Color.FromName("White");
            this.content.ForeColor = System.Drawing.Color.FromName("Black");
            this.content.BackColor = System.Drawing.Color.FromName("White");
        }

        public void CreateNew()
        {
            this.Text = string.Empty;
            this._position = 0;
        }

        // ==== Drag&Drop (initialized in constructor)

        private void content_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.Text))
                e.Effect = DragDropEffects.Copy;
        }
        
        private void content_DragDrop(object sender, DragEventArgs e)
        {
            e.Effect = DragDropEffects.Copy;
            string newcontent = e.Data.GetData(DataFormats.Text) as string;
            if (!string.IsNullOrEmpty(newcontent))
            {
                this.content.Text = newcontent;
            }
        }

        // ==== end of: Drag&Drop (initialized in constructor)

        public event EventHandler ContentChanged;
        public event EventHandler ContentSaveRequest;
        public event EventHandler SwapFocus;

        // ==== Private Methods

        private void ForceFocusSwap()
        {
            if (SwapFocus != null)
                SwapFocus(this, EventArgs.Empty);
        }

        private void FireContentChanged()
        {
            if ((ContentChanged != null) && (!this._isContentChanged))
            {
                ContentChanged(this, EventArgs.Empty);
                this._isContentChanged = true;
            }
        }

        public void NotifyContentSaved()
        {
            this._isContentChanged = false;
        }
        // ==== end of: Private Methods

        
        // ==== Public methods

        /// <summary>
        /// Sets proper font and encoding properties in ScintillaNet control, so you may use
        /// national characters (not only the pure latin)
        /// </summary>
        /// <param name="encodingName">name of the encoding that will be used</param>
        /// <param name="gdicharset">standard gdi charset - the value of ScintilaNet.GdiCharacterSet enum</param>
        /// <param name="defaultfont">selected font (font with proper encoding is being created)</param>
        public void SetEncodingsAndCharset( string encodingName, byte gdicharset, Font defaultfont )
        {
            ScintillaNet.CharacterSet charset = (ScintillaNet.CharacterSet)gdicharset;
            this.content.Styles.Default.CharacterSet = charset;
            Font f = new Font(defaultfont.FontFamily, defaultfont.Size, defaultfont.Style, defaultfont.Unit, gdicharset);
            this.content.Styles.Default.Font = f;
            this.content.UseFont = true;
            this.content.Encoding = System.Text.Encoding.GetEncoding(encodingName);
        }

        public void PasteFromClipboard()
        {
            this.content.Clipboard.Paste();
        }

        public void CopySelectedTextIntoClipboard()
        {
            this.content.Clipboard.Copy();
        }

        public void CutSelectedTextIntoClipboard()
        {
            this.content.Clipboard.Cut();
        }

        public void ReplaceText(string oldText, string newText)
        {
            this.content.Text.Replace(oldText, newText);
        }

        public void FindText()
        {
            this.content.FindReplace.ShowFind();
        }

        public void QuickFind()
        {
            this.content.FindReplace.IncrementalSearcher.Show();
        }

        public void Replace()
        {
            this.content.FindReplace.ShowReplace();
        }

        // ==== Public methods

        private void ResetContentUndoRedo()
        {
            this.content.UndoRedo.EmptyUndoBuffer();
        }
        
        
        private void tsmiPaste_Click(object sender, EventArgs e)
        {
            this.PasteFromClipboard();
        }

        private void Undo()
        {
            this.content.UndoRedo.Undo();
            // TODO: any smart way to detect if content is (still?) changed or back to saved-state?
            FireContentChanged();
        }

        private void Redo()
        {
            this.content.UndoRedo.Redo();
        }

        private void content_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
        {
            if (!e.Control || e.Alt)
                return;

            switch (e.KeyCode)
            {
                // A, C, X, V have to be handled here 
                // otherwise actions are doubled!
                case Keys.A:
                    this.content.Selection.SelectAll();
                    break;
                case Keys.C:
                    this.CopySelectedTextIntoClipboard();
                    break;
                case Keys.X:
                    this.CutSelectedTextIntoClipboard();
                    FireContentChanged();
                    break;
                case Keys.V:
                    this.PasteFromClipboard();
                    FireContentChanged();
                    break;

                case Keys.Y:
                    e.IsInputKey = true;
                    break;
                case Keys.Z:
                    e.IsInputKey = true;
                    break;

                case Keys.S:
                    e.IsInputKey = true;
                    break;
            }
        }

        private void content_KeyDown(object sender, KeyEventArgs e)
        {
            GetCurrentCursorPosition();

            if (e.Alt)
            {
                switch (e.KeyCode)
                {
                    case Keys.Right:
                        if (this.content.Margins[0].Width < MaxMargin)
                            this.content.Margins[0].Width += 2;
                        break;
                    case Keys.Left:
                        if (this.content.Margins[0].Width > MinMargin)
                            this.content.Margins[0].Width -= 2;
                        break;
                }
            }

            if (!e.Control || e.Alt)
                return;

            switch (e.KeyCode)
            {
                case Keys.Y:
                    this.Redo();
                    FireContentChanged();
                    break;
                case Keys.Z:
                    this.Undo();
                    FireContentChanged();
                    break;

                case Keys.T:
                    ForceFocusSwap();
                    break;
                case Keys.Tab:
                    ForceFocusSwap();
                    break;

                case Keys.F:
                    FindText();
                    break;
                case Keys.I:
                    this.QuickFind();
                    break;
                case Keys.H:
                    this.Replace();
                    break;

                case Keys.S:
                    if (ContentSaveRequest != null)
                        ContentSaveRequest(this, EventArgs.Empty);
                    break;
            }
        }

        private void GetCurrentCursorPosition()
        {
            _position = this.content.NativeInterface.GetCurrentPos();
        }

        public void RestoreCursorPosition()
        {
            this.content.NativeInterface.GotoPos(_position);
        }

        private void content_Click(object sender, EventArgs e)
        {
            GetCurrentCursorPosition();
        }

        private void content_CharAdded(object sender, ScintillaNet.CharAddedEventArgs e)
        {
             FireContentChanged();
        }
    }
}